namespace PHSRAG.Insight21.FormsManagement.Controls
{
	using System;
	using System.Data;
	using System.Drawing;
	using System.Web;
	using System.Web.UI;
	using System.Web.UI.WebControls;
	using System.Web.UI.HtmlControls;
	using System.Collections;
	using PHSRAG.Insight21.FormsManagement.Serialization;
	using System.Xml;
    using System.Xml.XPath;
	using System.Xml.Xsl;
	using System.IO;
	using System.Text.RegularExpressions;
	using System.Text;


	/// <summary>
	///	QuestionnaireViewer is a platform to display a questionnaire and gather answers.
	///	
	///	The LoadQuestionnaire function converts the questionnaire version and its answers, if any, into a serializable object (Form).
	///	This method MUST be called by the container control/page before the QuestionnaireViewer can be displayed to the user.
	/// </summary>
	public partial class QuestionnaireViewer : System.Web.UI.UserControl
	{
		#region Constants
		private const string FormKey = "FormKey_804DAFFE-A705-47c2-81B8-6FFF7008730F";
		private const int NewAnswerId = 0;
		#endregion

		#region Instance Variables (private)
		private bool allowNameEntry;
		private string answerName;
		private Policy.ICachePolicy	cachePolicy;
		private Form form;
		private bool continueLoading = true;
		StringBuilder errors = new StringBuilder();
		#endregion

		#region Properties
		/// <summary>
		/// Flag to determine whether the control should load or not 
		/// ex. in the case where there is no QuestionaireVersion to pass
		/// to the control, we need to tell it not to load, otherwise it will 
		/// throw an exception.
		/// </summary>
		public bool ContinueLoading
		{
			get { return continueLoading; } 
			set { continueLoading = value; }
		}

		/// <summary>
		/// Gets or sets whether or not the Name for the Questionnaire Answer should be displayed
		/// </summary>
		public bool AllowNameEntry
		{
			set { allowNameEntry = value; }
		}

		/// <summary>
		/// Gets or sets the name for the questionnaire answer.
		/// </summary>
		public string QuestionnaireAnswerName
		{
			get { return answerName; }
			set { answerName = value; }
		}

		/// <summary>
		/// The serializable form of the Questionnaire and its answers
		/// </summary>
		public Form Form
		{
			get { return form; }
			set { form = value; }
		}
		#endregion

		#region Events (page)
		/// <summary>
		/// Displays all the questions and answers, if any available.
		/// </summary>
		/// <param name="sender">Control that raised the event.</param>
		/// <param name="e">Arguments associated with the event.</param>
		protected void Page_Load(object sender, System.EventArgs e)
		{
			if ( !continueLoading ) 
				return;
			try
			{
				if (!IsPostBack)
				{
					//If name entry is allowed, then show the control
					//If name entry is not allowed, but a answer name is already present, then show the name.
					if (allowNameEntry || answerName != null)
					{
						nameLabel.Visible = true;
						questionnaireAnswerName.Visible = true;
						questionnaireAnswerName.Text = (answerName == null) ? string.Empty : answerName;
						if (questionnaireAnswerName.Text.Length > 0)
							questionnaireAnswerName.Columns = questionnaireAnswerName.Text.Length;
						if (!allowNameEntry)
						{
							questionnaireAnswerName.Enabled = false;
						}
					}
					else
					{
						answerNameTable.Visible = false;
						nameLabel.Visible = false;
						questionnaireAnswerName.Visible = false;
					}

                    if (form !=null)
					    DisplayQuestionnaire();
				}
				else
				{ 
					//Update the serializable objects on every postback. 
					//Postbacks occur only if there are any changes to multi-input and dropdownmultiselect control
					UpdateForm();
				}
				
			}
			catch(Exception ex)
			{
				throw new Exception("Error loading the Questionnaire Viewer control", ex);
			}

		}

		#endregion

		#region Web Form Designer generated code
		/// <summary>
		/// Page Initialization
		/// </summary>
		/// <param name="e">Arguments associated with the event.</param>
		override protected void OnInit(EventArgs e)
		{
			//
			// CODEGEN: This call is required by the ASP.NET Web Form Designer.
			//
			base.OnInit(e);
			cachePolicy = (Policy.ICachePolicy)Session[Policy.Names.CachePolicyKey];
			form = (Form)cachePolicy.GetCachedObject(FormKey);
			errorMessages.Text = string.Empty;
			exceptionsTable.Visible = false;

			//Because all the controls on the questionnaire viewer are dynamically generated,
			//whenever there is a postback, the page needs to be rebuilt so that the event causing
			//the postback can be executed. 
			if (IsPostBack && form != null )
			{
				DisplayQuestionnaire();
				
			}
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Updates the Form class with answers provided by the user. The update process is done recursively
		/// </summary>
		private void UpdateForm()
		{
			foreach(FormItem formItem in form.FormItems)
			{
				UpdateFormItems(formItem);
			}
		}

		
		/// <summary>
		/// Updates FormItems and its children, if any, with answers provided by the user 
		/// </summary>
		/// <param name="formItem">FormItem object whose answers are to be updated with those provided by the user</param>
		private void UpdateFormItems(FormItem formItem)
		{
			switch(formItem.Type)
			{
				case QuestionnaireItem.LabelItemType:
					break;
				case QuestionnaireItem.TextBoxItemType:
					TextBox textBoxQuestion = (TextBox)questionnaireContainer.FindControl(formItem.Name);
					if (textBoxQuestion.Text.Trim() != string.Empty)
						formItem.AddOrReplaceAnswer(textBoxQuestion.Text.Trim(), NewAnswerId);
					else
					{
						//if text is empty and answer was previously stored then delete the answer
						//if (formItem.Answers.Count == 1)
							formItem.DeleteAnswer();
					}
					break;
				case QuestionnaireItem.DropDownListItemType:
					DropDownList dropDownListQuestion = (DropDownList)questionnaireContainer.FindControl(formItem.Name);
					if (dropDownListQuestion.SelectedValue != string.Empty && dropDownListQuestion.SelectedItem.Text.IndexOf("--") == -1)
					{
						formItem.AddOrReplaceAnswer(dropDownListQuestion.SelectedValue, NewAnswerId);
					}
					else if (dropDownListQuestion.SelectedValue != string.Empty && dropDownListQuestion.SelectedItem.Text.IndexOf("--") >= 0)
					{
						formItem.DeleteAnswer();
					}
					break;
				case QuestionnaireItem.RadioButtonListItemType:
					RadioButtonList radioButtonQuestion = (RadioButtonList)questionnaireContainer.FindControl(formItem.Name);
					if (radioButtonQuestion.SelectedValue != string.Empty)
						formItem.AddOrReplaceAnswer(radioButtonQuestion.SelectedValue, NewAnswerId);
					break;
				case QuestionnaireItem.CheckBoxListItemType:
					foreach(FormItemOption option in formItem.Options)
					{
						CheckBox checkBoxQuestion = (CheckBox)FindControl(string.Format("{0}{1}", formItem.Name, option.OptionValue));
						if (checkBoxQuestion.Checked)
							formItem.AddCheckBoxAnswer(option.OptionValue, NewAnswerId);
						else
							formItem.RemoveCheckBoxAnswer(option.OptionValue);
					}
					break;
				case QuestionnaireItem.MultiSelectItemType:
					MultipleSelection multiSelectQuestion = (MultipleSelection)questionnaireContainer.FindControl(formItem.Name);
					//get the answer selected
					foreach(FormAnswer answer in formItem.Answers)
					{
						if (answer.Selected)
						{
							foreach(FormItem selectedFormItem in formItem.FormItems)
							{
								if (selectedFormItem.ParentValue == answer.Answer)
									UpdateFormItems(selectedFormItem);
							}
						}
					}
					formItem.SelectMultiSelectAnswer(multiSelectQuestion.SelectedListItemText);
					break;
				case QuestionnaireItem.DropDownMultiSelectItemType:
					DropDownMultipleSelection dropDownMultiSelectQuestion = (DropDownMultipleSelection)questionnaireContainer.FindControl(formItem.Name);
					//get the answer selected
					foreach(FormAnswer dropDownMultiSelectAnswer in formItem.Answers)
					{
						if (dropDownMultiSelectAnswer.Selected)
						{
							foreach(FormItem selectedDropDownFormItem in formItem.FormItems)
							{
								if (selectedDropDownFormItem.ParentValue == dropDownMultiSelectAnswer.Answer)
									UpdateFormItems(selectedDropDownFormItem);
							}
						}
					}
					formItem.SelectMultiSelectAnswer(dropDownMultiSelectQuestion.SelectedListItemText);
					break;
				case QuestionnaireItem.CustomValueSelectorItemType:
					CustomValueSelector customValueSelector = (CustomValueSelector)questionnaireContainer.FindControl(formItem.Name);
					//get the answer selected
					foreach (FormAnswer answer in formItem.Answers)
					{
						if (answer.Selected)
						{
							foreach (FormItem selectedFormItem in formItem.FormItems)
							{
								if (selectedFormItem.ParentValue == answer.Answer)
									UpdateFormItems(selectedFormItem);
							}
						}
					}
					formItem.SelectMultiSelectAnswer(customValueSelector.SelectedListItemValue);
					break;
			}
			;
			if (formItem.Type != QuestionnaireItem.MultiSelectItemType 
				&& formItem.Type != QuestionnaireItem.DropDownMultiSelectItemType
				&& formItem.Type != QuestionnaireItem.CustomValueSelectorItemType)
			{
				foreach(FormItem childFormItem in formItem.FormItems)
				{
					UpdateFormItems(childFormItem);
				}
			}
		}

		
		/// <summary>
		/// Serializes the Form class, transforms the xml generated into a string with html and web server control declarations, 
		/// parses these controls and adds them to the questionnaireContainer
		/// to be viewed by the user.
		/// </summary>
		public void DisplayQuestionnaire()
		{
			string formXml = QuestionnaireSerializer.Serialize(form);
			try
			{
                StringReader sr = new StringReader(formXml);
                XPathDocument xp = new XPathDocument(sr);
			
				// load xslt to do transformation
				XslCompiledTransform xsl = new XslCompiledTransform();
				xsl.Load(string.Format("{0}{1}", System.AppDomain.CurrentDomain.BaseDirectory, "FormsManagement/Questionnaire.xslt"));
			
				// get transformed results
				StringWriter sw = new StringWriter();
				xsl.Transform(xp, null /*xslt arguments */, sw);
			
				string result = sw.ToString().Replace("xmlns:asp=\"remove\"","").Replace("&lt;","<").Replace("&gt;",">").Replace("xmlns:uc1=\"remove\"","");

				sw.Close();
				Control parsedControls = Page.ParseControl(result);
				parsedControls.ID = "controlContainer";
				questionnaireContainer.Controls.Add(parsedControls);
				foreach(Control control in parsedControls.Controls)
				{
					RadioButtonList radioButtonList = control as RadioButtonList;
					if (radioButtonList != null)
					{
						radioButtonList.Attributes.Add("onclick", "javascript:OnRadioButtonIndexChanged('" + radioButtonList.ClientID + "', '" + radioButtonList.ID + "')");
						continue;
					}
					CheckBox checkBox = control as CheckBox;
					if (checkBox != null)
					{
						checkBox.Attributes.Add("onclick", "javascript:OnCheckBoxChanged('" + checkBox.ClientID + "', '" + checkBox.ID + "')");
						continue;
					}
					DropDownList dropDownList = control as DropDownList;
					if (dropDownList != null)
					{
						dropDownList.Attributes.Add("onchange", "javascript:OnDropDownSelectionChanged('" + dropDownList.ClientID + "', '" + dropDownList.ID + "')");
						continue;
					}
					
				}
			}
			catch(Exception ex)
			{
				throw new Exception("Failed to display the questionnaire.", ex);
			}
		}
		
		
		/// <summary>
		/// Converts the questionnaire version and the questionnaire answer, if any, to Form object
		/// </summary>
		/// <param name="questionnaireVersion">Questionnaire Version to be serialized</param>
		/// <param name="questionnaireAnswer">Questionnaire answer for the questionnaire version</param>
		public void LoadQuestionnaire(QuestionnaireVersion questionnaireVersion, QuestionnaireAnswer questionnaireAnswer)
		{
			form = questionnaireVersion.GetForm(questionnaireAnswer);

			cachePolicy.Cache(FormKey, form);
		}

		public bool ValidateRequiredQuestions(string failureMessage)
		{
			return ValidateRequiredQuestions(true, failureMessage);
		}

		public bool ValidateRequiredQuestions(bool itemizeErrors, string failureMessage)
		{
            foreach (FormItem formItem in form.FormItems)
			{
				CheckAnswersForRequiredQuestions(formItem, null);
			}
			if (errors != null && !string.IsNullOrEmpty(errors.ToString()))
			{
				errorMessages.Text = (itemizeErrors) ? string.Format("<ul>{0}</ul>", errors.ToString()) : failureMessage ;
				exceptionsTable.Visible = true;
				questionnaireContainer.Controls.Clear();
				DisplayQuestionnaire();
				return false;
			}
            else
			    return true;
		}

		public bool VerifyTextboxAnswers()
		{
			//verify textbox answer formats
			foreach (FormItem formItem in form.FormItems)
			{
				formItem.Highlight = false;
				ValidateAnswersForTextboxQuestion(formItem, null);
			}
			if (errors.Length > 0)
			{
				errorMessages.Text = "<ul>" + errors.ToString() + "</ul>";
				exceptionsTable.Visible = true;
				questionnaireContainer.Controls.Clear();
				DisplayQuestionnaire();
				return false;
			}
			return true;
		}
		/// <summary>
		/// If validation is enabled, then checks if required questions that are displayed are answered. 
		/// If so, saves the answers from FormAnswers to QuestionnaireAnswer
		/// </summary>
		/// <param name="questionnaireAnswer">QuestionnaireAnswer object</param>
		/// <returns>if validateRequiredQuestions property is set and required questions that are 
		/// displayed are not answered then returns false. Else returns true.</returns>
		public bool SaveQuestionnaire(QuestionnaireAnswer questionnaireAnswer)
		{
			form.DeriveAnswers(cachePolicy, questionnaireAnswer, false);

			return true;
		}


		/// <summary>
		/// Adds an answer to multiselect question & rebuilds the whole page.
		/// </summary>
		/// <param name="parentQuestionName">The parent question name for the multiselct question</param>
		/// <param name="parentQuestionValue">The parent question value for the multiselect question</param>
		/// <param name="questionName">The name of the multi select question</param>
		/// <param name="newItem">The new item added</param>
		public void AddMultiSelectItem(string parentQuestionName, string parentQuestionValue, string questionName, string newItem)
		{
				FormItem formItem = form.GetFormItem(parentQuestionName, parentQuestionValue, questionName);
			
				formItem.AddMultiSelectAnswer(newItem, NewAnswerId);
				questionnaireContainer.Controls.Clear();
				DisplayQuestionnaire();
		}


		/// <summary>
		/// REmoves an answer form the multi select question
		/// </summary>
		/// <param name="parentQuestionName">The parent question name for the multiselct question</param>
		/// <param name="parentQuestionValue">The parent question value for the multiselect question</param>
		/// <param name="questionName">The name of the multi select question</param>
		/// <param name="removedItem">The removed item</param>
		public void RemoveMultiSelectItem(string parentQuestionName, string parentQuestionValue, string questionName, string removedItem)
		{
				FormItem formItem = form.GetFormItem(parentQuestionName, parentQuestionValue, questionName);
				formItem.RemoveMultiSelectAnswer(removedItem);
				questionnaireContainer.Controls.Clear();
				DisplayQuestionnaire();
		}

		public bool CheckForValidationErrors()
		{
			CheckForValidationErrors(form.FormItems);
			if (errors.Length > 0)
			{
				errorMessages.Text = "<ul>" + errors.ToString() + "</ul>";
				exceptionsTable.Visible = true;
				return false;
			}
			return true;
		}
		/// <summary>
		/// Reloads the questions
		/// </summary>
		public void Refresh()
		{
			
			questionnaireContainer.Controls.Clear();
			DisplayQuestionnaire();
		}
		#endregion

		#region Private Methods

		private void CheckForValidationErrors(ArrayList formItems)
		{
			foreach (FormsManagement.Serialization.FormItem fi in formItems)
			{
				if (fi.Highlight)
					errors.Append(string.Format("<li><a href=\"#{0}\" style=\"color:Red\">{1}</a></li>", fi.Name, fi.ValidationMessage));
				CheckForValidationErrors(fi.FormItems);
			}
		}

		/// <summary>
		/// Check if displayed required questions are answered. 
		/// Questions might be marked as required but may not be displayed. This is possible if the question is a dependant question and the
		/// parent question is not answered in a way that would display the dependant question (each dependant question will be tied to a 
		/// specific answer of its parent).
		/// </summary>
		/// <param name="formItem">Question being verified</param>
		/// <param name="parentItem">Parent question of the question being verified</param>
		/// <returns>true if the validation succeeds</returns>
		private void CheckAnswersForRequiredQuestions(FormItem formItem, FormItem parentItem)
		{
            formItem.Highlight = false;
			if (formItem.Required)
			{
				if (formItem.Answers.Count == 0)
				{
					if (parentItem == null)
					{
						formItem.Highlight = true;
						errors.Append(string.Format("<li><a href=\"#{0}\" style=\"color:Red\">{1}</a></li>", formItem.Name, formItem.ValidationMessage));
					}
					else
					{
						foreach (FormAnswer parentAnswer in parentItem.Answers)
						{
							if (formItem.ParentValue == parentAnswer.Answer)
							{
								formItem.Highlight = true;
								errors.Append(string.Format("<li><a href=\"#{0}\" style=\"color:Red\">{1}</a></li>", formItem.Name, formItem.ValidationMessage));
							}
						}
					}
				}
			}

			foreach(FormItem childItem in formItem.FormItems)
			{
				bool verifyChild = false;
				foreach(FormAnswer parentAnswer in formItem.Answers)
				{
					if (parentAnswer.Answer == childItem.ParentValue)
					{
						verifyChild = true;
						break;
					}
				}
				if (verifyChild || childItem.ParentValue == null || childItem.ParentValue == string.Empty)
				{
					CheckAnswersForRequiredQuestions(childItem, formItem);
				}
			}
		}

		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="formItem"></param>
		/// <exception cref=" "></exception>
		private void ValidateAnswersForTextboxQuestion(FormItem formItem, FormItem parentItem)
		{
			if (formItem.Type == QuestionnaireItem.TextBoxItemType && formItem.Answers.Count > 0)
			{
				foreach (FormItemProperty prop in formItem.Attributes)
				{
					if (prop.AttributeName == TextBoxQuestion.PropertyNames.FormatProperty)
					{
						if (prop.AttributeValue == Enum.GetName(typeof(TextBoxQuestion.FormatOptions), TextBoxQuestion.FormatOptions.Alphabet)
							|| prop.AttributeValue == Enum.GetName(typeof(TextBoxQuestion.FormatOptions), TextBoxQuestion.FormatOptions.AlphaNumeric))
						{
							string pattern = string.Empty;
							if (prop.AttributeValue == Enum.GetName(typeof(TextBoxQuestion.FormatOptions), TextBoxQuestion.FormatOptions.Alphabet))
							{
								pattern = "^[a-zA-Z ]*$";
							}
							else //alphanumeric
							{
								pattern = "^[a-zA-Z0-9_ ]*$";
								
							}
							foreach (FormAnswer formAnswer in formItem.Answers)
							{
								if (!formAnswer.Deleted)
								{
									if (!Regex.Match(formAnswer.Answer, pattern).Success)
									{
										formItem.Highlight = true;
										errors.Append(string.Format("<li><a href=\"#{0}\" style=\"color:Red\">{1}</a></li>", formItem.Name, formItem.ValidationMessage));
										//return false;
									}
									else
										formItem.Highlight = false;
									break;
								}
							}
							//check the max chars only if the format match is successful. otherwise there are chances of the
							//bookmark flag getting overwritten.
							if (formItem.Highlight == false)
							{
								foreach (FormItemProperty dependantProp in formItem.Attributes)
								{
									if (dependantProp.AttributeName == TextBoxQuestion.PropertyNames.MaxCharsProperty)
									{
										foreach (FormAnswer formAnswer in formItem.Answers)
										{
											if (!formAnswer.Deleted)
											{
												if (formAnswer.Answer.Length > int.Parse(dependantProp.AttributeValue))
												{
													formItem.Highlight = true;
													errors.Append(string.Format("<li><a href=\"#{0}\" style=\"color:Red\">{1}</a></li>", formItem.Name, formItem.ValidationMessage));
													//return false;
												}
												else
													formItem.Highlight = false;
												break;
											}
										}
										break;
									}
								}
							}
						}
						else if (prop.AttributeValue == Enum.GetName(typeof(TextBoxQuestion.FormatOptions), TextBoxQuestion.FormatOptions.Numeric))
						{
							foreach (FormItemProperty dependantProp in formItem.Attributes)
							{
								if (dependantProp.AttributeName == TextBoxQuestion.PropertyNames.MaskProperty)
								{
									string mask = dependantProp.AttributeValue;
									Regex re = new Regex("^" + ConvertMaskToRegex(mask) + "$");
									foreach (FormAnswer formAnswer in formItem.Answers)
									{
										if (!formAnswer.Deleted)
										{
											if (!re.Match(formAnswer.Answer).Success)
											{
												formItem.Highlight = true;
												errors.Append(string.Format("<li><a href=\"#{0}\" style=\"color:Red\">{1}</a></li>", formItem.Name, formItem.ValidationMessage));
												//return false;
											}
											else
												formItem.Highlight = false;
										}
									}
									break;
								}
							}
							//check the max chars only if the format match is successful. otherwise there are chances of the
							//bookmark flag getting overwritten.
							if (formItem.Highlight == false)
							{
								//min and max value properties should be verified after the mask property
								foreach (FormItemProperty dependantProp in formItem.Attributes)
								{
									if (dependantProp.AttributeName == TextBoxQuestion.PropertyNames.MinValueProperty)
									{
										Decimal minValue = Convert.ToDecimal(dependantProp.AttributeValue);
										foreach (FormAnswer formAnswer in formItem.Answers)
										{
											if (!formAnswer.Deleted)
											{
												if (Convert.ToDecimal(formAnswer.Answer) < minValue)
												{
													formItem.Highlight = true;
													errors.Append(string.Format("<li><a href=\"#{0}\" style=\"color:Red\">{1}</a></li>", formItem.Name, formItem.ValidationMessage));
													//return false;
												}
												else
													formItem.Highlight = false;
												break;
											}
										}
									}
									if (dependantProp.AttributeName == TextBoxQuestion.PropertyNames.MaxValueProperty)
									{
										Decimal maxValue = Convert.ToDecimal(dependantProp.AttributeValue);
										foreach (FormAnswer formAnswer in formItem.Answers)
										{
											if (!formAnswer.Deleted)
											{
												if (Convert.ToDecimal(formAnswer.Answer) > maxValue)
												{
													formItem.Highlight = true;
													errors.Append(string.Format("<li><a href=\"#{0}\" style=\"color:Red\">{1}</a></li>", formItem.Name, formItem.ValidationMessage));
													//return false;
												}
												else
													formItem.Highlight = false;
												break;
											}
										}
									}
								}
							}
						}
						break;
					}
				}
			}

			foreach (FormItem childItem in formItem.FormItems)
			{
				bool verifyChild = false;
				foreach (FormAnswer parentAnswer in formItem.Answers)
				{
					if (parentAnswer.Answer == childItem.ParentValue)
					{
						verifyChild = true;
						break;
					}
				}
				if (verifyChild || childItem.ParentValue == null || childItem.ParentValue == string.Empty)
				{
					childItem.Highlight = false;
					ValidateAnswersForTextboxQuestion(childItem, formItem);
					//return false;
				}
			}

			//return (errors.Length <= 0);
		}

		private string ConvertMaskToRegex(string mask)
		{
			StringBuilder regex = new StringBuilder();
			string temp = mask.Substring(0, 1);
			if (temp == "+" || temp == "-")
			{
				regex.Append(@"[-+]?");
			}
			MatchCollection matches = Regex.Matches(mask, "[0-9]+");
			if (matches.Count > 0)
			{
				regex.Append("[0-9]{1," + matches[0].Length + "}");
				if (matches.Count > 1)
				{
					regex.Append(@"(\.[0-9]{1," + matches[1].Length + "})?");
				}
			}
			return regex.ToString();
		}
		#endregion
	}
}
